package com.wywah.yunduo.security.supports;

import cn.hutool.json.JSONUtil;
import com.wywah.yunduo.orm.bean.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.security.Principal;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Configuration
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private static final String AUTHORIZATION = "yunduo";
    private final RedisTemplate<String, Object> redisTemplate;
    @Override
    public void save(OAuth2Authorization authorization) {
        Assert.notNull(authorization, "authorization cannot be null");
        String state = authorization.getAttribute(OAuth2ParameterNames.STATE);
        if(StringUtils.hasText(state)){
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, state), authorization);
        }

        String code = authorization.getAttribute(OAuth2ParameterNames.CODE);
        if(StringUtils.hasText(code)){
            OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);
            OAuth2AuthorizationCode token = authorizationCode.getToken();
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(token.getIssuedAt()), token.getExpiresAt());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, token.getTokenValue()), authorization, between, TimeUnit.MINUTES);
        }


        OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
        if(Objects.nonNull(refreshToken)){
            OAuth2RefreshToken token = refreshToken.getToken();
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(token.getIssuedAt()), token.getExpiresAt());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, token.getTokenValue()), authorization, between, TimeUnit.MINUTES);
        }

        OAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();
        if (Objects.nonNull(accessToken)) {
            OAuth2AccessToken token = accessToken.getToken();
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(token.getIssuedAt()), token.getExpiresAt());
            redisTemplate.setValueSerializer(RedisSerializer.java());
            AbstractAuthenticationToken authenticationToken = authorization.getAttribute(Principal.class.getName());
            if (authenticationToken != null) {
                String jsonStr = JSONUtil.toJsonStr(authenticationToken.getPrincipal());
                redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, token.getTokenValue()), jsonStr, between, TimeUnit.MINUTES);
            }else {
                redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, token.getTokenValue()), authorization, between, TimeUnit.MINUTES);
            }
        }
    }

    @Override
    public void remove(OAuth2Authorization authorization) {
        logger.info("===RedisOAuth2AuthorizationService remove");
        Assert.notNull(authorization, "authorization cannot be null");
        List<String> keys = new ArrayList<>();
        String state = authorization.getAttribute(OAuth2ParameterNames.STATE);
        if(StringUtils.hasText(state)){
            keys.add(buildKey(OAuth2ParameterNames.STATE, state));
        }

        String code = authorization.getAttribute(OAuth2ParameterNames.CODE);
        if(StringUtils.hasText(code)){
            OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization.getToken(OAuth2AuthorizationCode.class);
            OAuth2AuthorizationCode token = authorizationCode.getToken();
            keys.add(buildKey(OAuth2ParameterNames.CODE, token.getTokenValue()));
        }

        String refreshToken = authorization.getAttribute(OAuth2ParameterNames.REFRESH_TOKEN);
        if(StringUtils.hasText(refreshToken)){
            OAuth2RefreshToken token = authorization.getRefreshToken().getToken();
            keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, token.getTokenValue()));
        }

        String accessToken = authorization.getAttribute(OAuth2ParameterNames.ACCESS_TOKEN);
        if (StringUtils.hasText(accessToken)) {
            OAuth2AccessToken token = authorization.getAccessToken().getToken();
            keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, token.getTokenValue()));
        }
        redisTemplate.delete(keys);
    }

    @Override
    public OAuth2Authorization findById(String id) {
        return null;
    }

    @Override
    public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
        Assert.hasText(token, "token cannot be empty");
        Assert.notNull(tokenType, "tokenType cannot be empty");
        redisTemplate.setValueSerializer(RedisSerializer.java());
        return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
    }


    private String buildKey(String type, String id) {
        return String.format("%s:%s:%s", AUTHORIZATION, type, id);
    }

    public RedisOAuth2AuthorizationService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
}
